package com.entertainment.ticketing.service;

import com.entertainment.ticketing.dto.EventInfoDTO;
import com.entertainment.ticketing.dto.PageResponse;
import com.entertainment.ticketing.dto.EventTicketsBatchUpdateRequest;
import com.entertainment.ticketing.dto.TicketInfoDTO;
import com.entertainment.ticketing.entity.EventInfo;
import com.entertainment.ticketing.entity.TicketInfo;
import com.entertainment.ticketing.repository.EventInfoRepository;
import com.entertainment.ticketing.repository.TicketInfoRepository;
import org.springframework.data.domain.Page;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.http.HttpStatus;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;

import java.time.LocalDateTime;
import java.util.List;
import java.util.ArrayList;
import java.util.Objects;
import java.util.stream.Collectors;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@Service
public class EventService {
    private final EventInfoRepository eventRepository;
    private final TicketInfoRepository ticketRepository;

    public EventService(EventInfoRepository eventRepository, TicketInfoRepository ticketRepository) {
        this.eventRepository = eventRepository;
        this.ticketRepository = ticketRepository;
    }

    public List<EventInfo> listAll() {
        return eventRepository.findAll();
    }

    public EventInfoDTO getById(Long id) {
        EventInfo e = eventRepository.findById(id)
                .orElseThrow(() -> new org.springframework.web.server.ResponseStatusException(
                        org.springframework.http.HttpStatus.NOT_FOUND, "演出不存在"));
        return toDTO(e);
    }

    @Cacheable(cacheNames = "recentEvents", key = "#size")
    public java.util.List<EventInfoDTO> recent(int size) {
        int safeSize = Math.min(Math.max(1, size), 50);
        org.springframework.data.domain.Pageable pageable = org.springframework.data.domain.PageRequest.of(
                0, safeSize, org.springframework.data.domain.Sort.by(org.springframework.data.domain.Sort.Direction.ASC, "startTime")
        );
        java.time.LocalDateTime now = java.time.LocalDateTime.now();
        org.springframework.data.domain.Page<EventInfo> page = eventRepository.findByStatusAndStartTimeAfterOrderByStartTimeAsc((byte)1, now, pageable);
        return page.getContent().stream().map(this::toDTO).collect(java.util.stream.Collectors.toList());
    }

    public EventInfoDTO create(EventInfoDTO dto) {
        validateEventForCreate(dto);
        EventInfo e = new EventInfo();
        e.setTitle(dto.getTitle());
        e.setDescription(dto.getDescription());
        e.setCategory(dto.getCategory());
        e.setVenue(dto.getVenue());
        e.setAddress(dto.getAddress());
        e.setStartTime(dto.getStartTime());
        e.setEndTime(dto.getEndTime());
        e.setPosterUrl(dto.getPosterUrl());
        e.setStatus(dto.getStatus() != null ? dto.getStatus() : 1);
        e.setTotalSeats(dto.getTotalSeats() != null ? dto.getTotalSeats() : 0);
        e.setAvailableSeats(dto.getAvailableSeats() != null ? dto.getAvailableSeats() : 0);
        EventInfo saved = eventRepository.save(e);
        return toDTO(saved);
    }

    public EventInfoDTO update(Long id, EventInfoDTO dto) {
        EventInfo e = eventRepository.findById(id)
                .orElseThrow(() -> new ResponseStatusException(
                        HttpStatus.NOT_FOUND, "演出不存在"));
        validateEventForUpdate(dto, e);
        if (dto.getTitle() != null) e.setTitle(dto.getTitle());
        if (dto.getDescription() != null) e.setDescription(dto.getDescription());
        if (dto.getCategory() != null) e.setCategory(dto.getCategory());
        if (dto.getVenue() != null) e.setVenue(dto.getVenue());
        if (dto.getAddress() != null) e.setAddress(dto.getAddress());
        if (dto.getStartTime() != null) e.setStartTime(dto.getStartTime());
        if (dto.getEndTime() != null) e.setEndTime(dto.getEndTime());
        if (dto.getPosterUrl() != null) e.setPosterUrl(dto.getPosterUrl());
        if (dto.getStatus() != null) e.setStatus(dto.getStatus());
        if (dto.getTotalSeats() != null) e.setTotalSeats(dto.getTotalSeats());
        if (dto.getAvailableSeats() != null) e.setAvailableSeats(dto.getAvailableSeats());
        EventInfo saved = eventRepository.save(e);
        return toDTO(saved);
    }

    public void delete(Long id) {
        if (!eventRepository.existsById(id)) {
            throw new org.springframework.web.server.ResponseStatusException(
                    org.springframework.http.HttpStatus.NOT_FOUND, "演出不存在");
        }
        eventRepository.deleteById(id);
    }

    public PageResponse<EventInfoDTO> search(
            String keyword,
            String category,
            Byte status,
            LocalDateTime startFrom,
            LocalDateTime startTo,
            int page,
            int size,
            String sortBy,
            Sort.Direction order
    ) {
        Specification<EventInfo> spec = Specification.where(null);

        if (keyword != null && !keyword.isBlank()) {
            String kw = "%" + keyword.trim().toLowerCase() + "%";
            spec = spec.and((root, query, cb) -> cb.or(
                    cb.like(cb.lower(root.get("title")), kw),
                    cb.like(cb.lower(root.get("description").as(String.class)), kw),
                    cb.like(cb.lower(root.get("venue")), kw),
                    cb.like(cb.lower(root.get("address")), kw)
            ));
        }
        if (category != null && !category.isBlank()) {
            String cat = category.trim();
            spec = spec.and((root, query, cb) -> cb.equal(cb.lower(root.get("category")), cat.toLowerCase()));
        }
        if (status != null) {
            spec = spec.and((root, query, cb) -> cb.equal(root.get("status"), status));
        }
        if (startFrom != null) {
            spec = spec.and((root, query, cb) -> cb.greaterThanOrEqualTo(root.get("startTime"), startFrom));
        }
        if (startTo != null) {
            spec = spec.and((root, query, cb) -> cb.lessThanOrEqualTo(root.get("startTime"), startTo));
        }

        int safePage = Math.max(0, page);
        int safeSize = Math.min(Math.max(1, size), 100);

        // 支持新增排序：lowestPrice、popularity
        Page<EventInfo> pageResult;
        String kwParam = (keyword != null && !keyword.isBlank()) ? keyword.trim() : null;
        String catParam = (category != null && !category.isBlank()) ? category.trim() : null;

        if ("lowestPrice".equalsIgnoreCase(sortBy)) {
            Pageable pageable = PageRequest.of(safePage, safeSize);
            if (order == Sort.Direction.DESC) {
                pageResult = eventRepository.searchOrderByLowestPriceDesc(kwParam, catParam, status, startFrom, startTo, pageable);
            } else {
                pageResult = eventRepository.searchOrderByLowestPriceAsc(kwParam, catParam, status, startFrom, startTo, pageable);
            }
        } else if ("popularity".equalsIgnoreCase(sortBy)) {
            Pageable pageable = PageRequest.of(safePage, safeSize);
            if (order == Sort.Direction.DESC) {
                pageResult = eventRepository.searchOrderByPopularityDesc(kwParam, catParam, status, startFrom, startTo, pageable);
            } else {
                pageResult = eventRepository.searchOrderByPopularityAsc(kwParam, catParam, status, startFrom, startTo, pageable);
            }
        } else {
            java.util.List<String> allowedSorts = java.util.List.of("startTime", "createdAt", "title", "status");
            String safeSortBy = allowedSorts.contains(sortBy) ? sortBy : "startTime";
            Pageable pageable = PageRequest.of(safePage, safeSize, Sort.by(order, safeSortBy));
            pageResult = eventRepository.findAll(spec, pageable);
        }

        List<EventInfoDTO> content = pageResult.getContent().stream()
                .map(this::toDTO)
                .collect(Collectors.toList());

        return new PageResponse<>(
                content,
                pageResult.getNumber(),
                pageResult.getSize(),
                pageResult.getTotalElements(),
                pageResult.getTotalPages(),
                sortBy,
                order.name()
        );
    }

    private EventInfoDTO toDTO(EventInfo e) {
        java.math.BigDecimal lowest = ticketRepository.getLowestPriceByEventId(e.getId());
        return EventInfoDTO.builder()
                .id(e.getId())
                .title(e.getTitle())
                .description(e.getDescription())
                .category(e.getCategory())
                .venue(e.getVenue())
                .address(e.getAddress())
                .startTime(e.getStartTime())
                .endTime(e.getEndTime())
                .posterUrl(e.getPosterUrl())
                .status(e.getStatus())
                .totalSeats(e.getTotalSeats())
                .availableSeats(e.getAvailableSeats())
                .lowestPrice(lowest)
                .build();
    }

    public EventInfoDTO uploadPoster(Long eventId, MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "请上传海报文件");
        }
        EventInfo event = eventRepository.findById(eventId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "演出不存在"));

        String original = file.getOriginalFilename();
        String ext = (original != null && original.contains("."))
                ? original.substring(original.lastIndexOf('.') + 1).toLowerCase()
                : "png";
        java.util.List<String> allowed = java.util.List.of("png", "jpg", "jpeg", "webp");
        if (!allowed.contains(ext)) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "仅支持png/jpg/jpeg/webp格式");
        }
        try {
            Path dir = Paths.get("uploads", "posters");
            if (!Files.exists(dir)) {
                Files.createDirectories(dir);
            }
            String filename = "event-" + eventId + "-" + System.currentTimeMillis() + "." + ext;
            Path target = dir.resolve(filename);

            // 使用流写入以降低内存占用，并兼容不同平台临时文件行为
            try (java.io.InputStream in = file.getInputStream()) {
                java.nio.file.Files.copy(in, target, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
            }

            event.setPosterUrl("/uploads/posters/" + filename);
            EventInfo saved = eventRepository.save(event);

            // 记录上传日志（log/yyyyMMddHHmmss.log）
            try {
                Path logDir = Paths.get("log");
                Files.createDirectories(logDir);
                String ts = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date());
                Path logFile = logDir.resolve(ts + ".log");
                String line = "Poster uploaded: eventId=" + eventId + ", file=" + target.toAbsolutePath() + System.lineSeparator();
                java.nio.file.Files.writeString(logFile, line, java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.APPEND);
            } catch (Exception ignore) {}

            return toDTO(saved);
        } catch (Exception ex) {
            // 记录错误日志
            try {
                Path logDir = Paths.get("log");
                Files.createDirectories(logDir);
                String ts = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date());
                Path logFile = logDir.resolve(ts + ".log");
                String line = "Poster upload failed: eventId=" + eventId + ", error=" + ex.getMessage() + System.lineSeparator();
                java.nio.file.Files.writeString(logFile, line, java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.APPEND);
            } catch (Exception ignore) {}
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "海报上传失败: " + ex.getMessage());
        }
    }

    public java.util.List<TicketInfoDTO> updateTicketsBatch(Long eventId, EventTicketsBatchUpdateRequest req) {
        EventInfo event = eventRepository.findById(eventId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "演出不存在"));

        // 删除请求
        if (req.getDeleteIds() != null && !req.getDeleteIds().isEmpty()) {
            List<TicketInfo> toDelete = ticketRepository.findAllById(req.getDeleteIds());
            for (TicketInfo t : toDelete) {
                if (!Objects.equals(t.getEvent().getId(), eventId)) {
                    throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "存在不属于当前演出的票，删除中止");
                }
                if (t.getStatus() != null && t.getStatus() == 2) {
                    throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "已售票不可删除");
                }
            }
            // 更新座位统计：删除可售票需要减少availableSeats
            long removableAvailable = toDelete.stream().filter(x -> x.getStatus() != null && x.getStatus() == 1).count();
            event.setTotalSeats(Math.max(0, event.getTotalSeats() - toDelete.size()));
            event.setAvailableSeats(Math.max(0, event.getAvailableSeats() - (int) removableAvailable));
            ticketRepository.deleteAll(toDelete);
        }

        List<TicketInfoDTO> result = new ArrayList<>();
        if (req.getTickets() != null) {
            for (TicketInfoDTO dto : req.getTickets()) {
                validateTicketEditable(dto);
                if (dto.getId() == null) {
                    TicketInfo t = new TicketInfo();
                    t.setEvent(event);
                    t.setTicketType(dto.getTicketType());
                    t.setPrice(dto.getPrice());
                    t.setSeatArea(dto.getSeatArea());
                    t.setSeatRow(dto.getSeatRow());
                    t.setSeatNumber(dto.getSeatNumber());
                    t.setStatus(dto.getStatus() != null ? dto.getStatus() : 1);
                    TicketInfo saved = ticketRepository.save(t);
                    // 新增可售票则增加座位
                    event.setTotalSeats(event.getTotalSeats() + 1);
                    if (saved.getStatus() == 1) {
                        event.setAvailableSeats(event.getAvailableSeats() + 1);
                    }
                    result.add(toTicketDTO(saved));
                } else {
                    TicketInfo t = ticketRepository.findById(dto.getId())
                            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "票务不存在"));
                    if (!Objects.equals(t.getEvent().getId(), eventId)) {
                        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "票与演出不匹配");
                    }
                    if (t.getStatus() != null && t.getStatus() == 2) {
                        // 已售票不允许编辑核心字段
                        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "已售票不可编辑");
                    }
                    if (dto.getTicketType() != null) t.setTicketType(dto.getTicketType());
                    if (dto.getPrice() != null) t.setPrice(dto.getPrice());
                    if (dto.getSeatArea() != null) t.setSeatArea(dto.getSeatArea());
                    if (dto.getSeatRow() != null) t.setSeatRow(dto.getSeatRow());
                    if (dto.getSeatNumber() != null) t.setSeatNumber(dto.getSeatNumber());
                    if (dto.getStatus() != null) {
                        // 维护 availableSeats（仅当从可售变为不可售/锁定时减少）
                        if (t.getStatus() == 1 && dto.getStatus() != 1) {
                            event.setAvailableSeats(Math.max(0, event.getAvailableSeats() - 1));
                        }
                        if (t.getStatus() != 1 && dto.getStatus() == 1) {
                            event.setAvailableSeats(event.getAvailableSeats() + 1);
                        }
                        t.setStatus(dto.getStatus());
                    }
                    TicketInfo saved = ticketRepository.save(t);
                    result.add(toTicketDTO(saved));
                }
            }
        }
        eventRepository.save(event);
        return result;
    }

    private void validateEventForCreate(EventInfoDTO dto) {
        if (dto.getTitle() == null || dto.getTitle().isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "标题不能为空");
        }
        if (dto.getCategory() == null || dto.getCategory().isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "类别不能为空");
        }
        if (dto.getVenue() == null || dto.getVenue().isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "场馆不能为空");
        }
        if (dto.getAddress() == null || dto.getAddress().isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "地址不能为空");
        }
        if (dto.getStartTime() == null) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "开始时间不能为空");
        }
        if (dto.getEndTime() == null) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "结束时间不能为空");
        }
        if (dto.getEndTime().isBefore(dto.getStartTime())) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "结束时间需晚于开始时间");
        }
        int total = dto.getTotalSeats() != null ? dto.getTotalSeats() : 0;
        int available = dto.getAvailableSeats() != null ? dto.getAvailableSeats() : 0;
        if (total < 0 || available < 0) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "座位数不能为负数");
        }
        if (available > total) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "可用座位不能大于总座位");
        }
        if (dto.getTitle().length() > 200) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "标题长度不能超过200");
        }
        if (dto.getVenue().length() > 200) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "场馆长度不能超过200");
        }
        if (dto.getAddress().length() > 500) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "地址长度不能超过500");
        }
    }

    private void validateEventForUpdate(EventInfoDTO dto, EventInfo existed) {
        if (dto.getTitle() != null && dto.getTitle().isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "标题不能为空");
        }
        if (dto.getVenue() != null && dto.getVenue().isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "场馆不能为空");
        }
        if (dto.getAddress() != null && dto.getAddress().isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "地址不能为空");
        }
        if (dto.getStartTime() != null && dto.getEndTime() != null) {
            if (dto.getEndTime().isBefore(dto.getStartTime())) {
                throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "结束时间需晚于开始时间");
            }
        } else if (dto.getEndTime() != null) {
            LocalDateTime start = dto.getStartTime() != null ? dto.getStartTime() : existed.getStartTime();
            if (dto.getEndTime().isBefore(start)) {
                throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "结束时间需晚于开始时间");
            }
        }
        if (dto.getTotalSeats() != null && dto.getTotalSeats() < 0) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "总座位不能为负数");
        }
        if (dto.getAvailableSeats() != null && dto.getAvailableSeats() < 0) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "可用座位不能为负数");
        }
        int total = dto.getTotalSeats() != null ? dto.getTotalSeats() : existed.getTotalSeats();
        int available = dto.getAvailableSeats() != null ? dto.getAvailableSeats() : existed.getAvailableSeats();
        if (available > total) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "可用座位不能大于总座位");
        }
        if (dto.getTitle() != null && dto.getTitle().length() > 200) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "标题长度不能超过200");
        }
        if (dto.getVenue() != null && dto.getVenue().length() > 200) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "场馆长度不能超过200");
        }
        if (dto.getAddress() != null && dto.getAddress().length() > 500) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "地址长度不能超过500");
        }
    }

    private void validateTicketEditable(TicketInfoDTO dto) {
        if (dto.getTicketType() == null || dto.getTicketType().isBlank()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "票种不能为空");
        }
        if (dto.getPrice() == null || dto.getPrice().compareTo(java.math.BigDecimal.ZERO) < 0) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "票价需为非负数");
        }
        if (dto.getSeatArea() != null && dto.getSeatArea().length() > 50) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "区块长度不能超过50");
        }
        if (dto.getSeatRow() != null && dto.getSeatRow().length() > 10) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "排号长度不能超过10");
        }
        if (dto.getSeatNumber() != null && dto.getSeatNumber().length() > 10) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "座号长度不能超过10");
        }
    }

    private TicketInfoDTO toTicketDTO(TicketInfo t) {
        return TicketInfoDTO.builder()
                .id(t.getId())
                .eventId(t.getEvent().getId())
                .ticketType(t.getTicketType())
                .price(t.getPrice())
                .seatArea(t.getSeatArea())
                .seatRow(t.getSeatRow())
                .seatNumber(t.getSeatNumber())
                .status(t.getStatus())
                .build();
    }
}